package com.g.ocache.impl.store.expiration;

import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.log4j.Logger;

import com.g.ocache.utils.XThreadFactory;


/**
 * 
 * @author zhangyf
 * @param <K>
 * @param <V>
 */
public final class BasicExpirationEvictor<K, V> implements ExpirationEvictor<K, V> {
	//
	private static final Logger LOGGER = Logger.getLogger(BasicExpirationEvictor.class);
	
	//
	private final int interval;
	private final Expirable<K, V> expirable;
	private final ScheduledExecutorService executor;
	private final AtomicBoolean verbose = new AtomicBoolean(true);
	private final AtomicBoolean started = new AtomicBoolean(false);
	
	/**
	 * 
	 */
	public BasicExpirationEvictor(Expirable<K, V> expirable, int interval) {
		this(expirable, interval, new XThreadFactory(BasicExpirationEvictor.class.getSimpleName(), true));
	}
	
	public BasicExpirationEvictor(Expirable<K, V> expirable, int interval, ThreadFactory tf) {
		this(expirable, interval, Executors.newScheduledThreadPool(1, tf));
	}
	
	public BasicExpirationEvictor(Expirable<K, V> expirable, int interval, ScheduledExecutorService executor) {
		this.expirable = expirable;
		this.interval = interval;
		this.executor = executor;
	}
	
	/**
	 * 
	 */
	public String toString() {
		return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
		.append("interval", interval)
		.append("verbose", isVerbose())
		.append("running", isRunning())
		.append("expirable", expirable).toString();
	}
	
	/**
	 * 
	 */
	public boolean isRunning() {
		return started.get();
	}
	
	public void start() {
		// Precondition checking
		if(!started.compareAndSet(false, true)) {
			throw new IllegalStateException(this + " has already been started");
		}
		
		//
		this.executor.scheduleWithFixedDelay(new Task(), interval, interval, TimeUnit.SECONDS);
	}

	public void stop() {
		// Precondition checking
		if(!started.compareAndSet(true, false)) {
			return;
		}
		
		//
		executor.shutdownNow();
	}
	
	/**
	 * 
	 */
	public int getInterval() {
		return interval;
	}
	
	public boolean isVerbose() {
		return verbose.get();
	}

	public void setVerbose(boolean verbose) {
		this.verbose.set(verbose);
	}
	
	public Expirable<K, V> getExpirable() {
		return expirable;
	}
	
	/**
	 * 
	 */
	public static class FactoryImpl<K, V> implements Factory<K, V> {
		//
		private int interval;
		
		/**
		 * 
		 */
		public FactoryImpl(int interval) {
			this.interval = interval;
		}

		/**
		 * 
		 */
		public ExpirationEvictor<K, V> create(Expirable<K, V> expirable) {
			return new BasicExpirationEvictor<K, V>(expirable, interval);
		}

		/**
		 * 
		 */
		public int getInterval() {
			return interval;
		}

		public void setInterval(int interval) {
			this.interval = interval;
		}
	}
	
	private class Task implements Runnable {
		
		public void run() {
			try {
				//
				if(expirable == null) {
					LOGGER.warn("can not schedule " + this + " because expirable is null");
					return;
				}
				
				//
				long now = System.currentTimeMillis();
				Map<K, V> expired = expirable.detect();
				
				//
				if(isVerbose() && LOGGER.isInfoEnabled()) {
					LOGGER.info(expirable + " was successfully evicted, count: " + expired.size() + ", time elapsed: " + (System.currentTimeMillis() - now));
				}
			} catch(Exception e) {
				LOGGER.error("unhandled exception in expiration evictor: " + BasicExpirationEvictor.this, e);
			}
		}
	}
}
